package com.mapforge.gui.map;

import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;

import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.SwingConstants;

import com.mapforge.gui.interfaces.TileMapEditorGUIInterface;
import com.mapforge.map.TileMap;

/**
 * JScrollPane for a TileMapPanel. Allows a TileMap to be displayed in a scroll
 * pane by automatically adjusting the clip when the scroll panel is scrolled.
 * 
 * @author William Morrison
 * 
 */
public class TileMapPanelScrollPane extends JScrollPane implements
		AdjustmentListener {
	/**
	 * Default serial version ID, here to remove warnings.
	 */
	private static final long serialVersionUID = 1L;

	private ComponentListener componentListener;

	private TileMapEditorGUIInterface tileMapPanel;
	private int horizonShift;
	private int verticalShift;

	public TileMapPanelScrollPane() {
		this(null);
	}

	public TileMapPanelScrollPane(TileMapEditorGUIInterface tileMapPanel) {
		super((Component) tileMapPanel);
		componentListener = new TileMapScrollPanelComponentListener();
		this.addComponentListener(componentListener);
		if (tileMapPanel != null)
			setTileMapPanel(tileMapPanel);
		getHorizontalScrollBar().addAdjustmentListener(this);
		getVerticalScrollBar().addAdjustmentListener(this);
		setAutoscrolls(true);
	}

	public void setTileMapPanel(TileMapEditorGUIInterface tileMapPanel) {
		if (tileMapPanel == null) {
			throw new NullPointerException("TileMapPanel cannot be null");
		}
		this.tileMapPanel = tileMapPanel;
		if (tileMapPanel.getTiledMap() != null) {
			TileMap map = tileMapPanel.getTiledMap();
			map.setClip(0, 0, getWidth(), getHeight());
			getHorizontalScrollBar().setBlockIncrement(
					horizonShift = map.getTileWidth());
			getVerticalScrollBar().setBlockIncrement(
					verticalShift = map.getTileHeight());
		}
		revalidate();
	}

	public void setHorizontalBlockIncrement(int inc) {
		getHorizontalScrollBar().setBlockIncrement(horizonShift = inc);
	}

	public void setVerticalBlockIncrement(int inc) {
		getVerticalScrollBar().setBlockIncrement(verticalShift = inc);
	}

	public int getHorizontalBlockIncrement() {
		return horizonShift;
	}

	public int getVerticalBlockIncrement() {
		return verticalShift;
	}

	/**
	 * Overidden method from interface AdjustmentListener.
	 * 
	 * This method works by first calculating the current viewport from
	 * horizontal and vertical bar values. This gives us our upper left
	 * coordinate. Next the viewport width is calculated ensuring its less than
	 * or equal to mapWidth. If it isn't, viewport width is equal to the actual
	 * viewport width. Height for the viewport is calculated in the same way.
	 * 
	 * All this ensures our JScrollPane's bars are in proportion to what the
	 * map's displaying. Next we set the center of the viewport as the center of
	 * our map so that when calling map.render, only visible tiles are rendered.
	 */
	public void adjustmentValueChanged(AdjustmentEvent e) {
		if (tileMapPanel.getTiledMap() != null) {
			Rectangle seeRect = getVisibleRect();
			JScrollBar horizBar = getHorizontalScrollBar();
			JScrollBar vertBar = getVerticalScrollBar();
			int x = horizBar.getValue();
			int y = vertBar.getValue();
			TileMap map = tileMapPanel.getTiledMap();
			int w = (int) (seeRect.width + x + horizonShift);
			int h = (int) (seeRect.height + y + verticalShift);
			// convert back to tile map unit measurements, necessary since
			// scroll pane measurements are scaled
			w = (int) (w > map.getWidth() ? map.getWidth() - x : w - x);
			h = (int) (h > map.getHeight() ? map.getHeight() - y : h - y);
			// Rectangle rect = new Rectangle(x, y, w, h);
			map.setClip(x, y, w, h);
			// w = map.getTileWidth();
			// h = map.getTileHeight();
			// map.setToCenter((int) rect.getCenterX() - w / 2, (int) rect
			// .getCenterY()
			// - h / 2, w, h);
		}
	}

	public int getScrollableUnitIncrement(Rectangle visibleRect,
			int orientation, int direction) {
		// Get the current position.
		int currentPosition = 0;
		if (orientation == SwingConstants.HORIZONTAL) {
			currentPosition = visibleRect.x;
		} else {
			currentPosition = visibleRect.y;
		}
		TileMap map = tileMapPanel.getTiledMap();
		int inc = (int) (orientation == SwingConstants.HORIZONTAL ? map
				.getTileWidth() : map.getTileHeight());
		// Return the number of pixels between currentPosition
		// and the nearest tick mark in the indicated direction.
		if (direction < 0) {
			int newPosition = currentPosition - (currentPosition / inc) * inc;
			return (newPosition == 0) ? inc : newPosition;
		} else {
			return ((currentPosition / inc) + 1) * inc - currentPosition;
		}
	}

	private class TileMapScrollPanelComponentListener implements
			ComponentListener {

		public void componentHidden(ComponentEvent arg0) {
			// TODO Auto-generated method stub

		}

		public void componentMoved(ComponentEvent arg0) {
			// TODO Auto-generated method stub

		}

		public void componentResized(ComponentEvent e) {
			if (tileMapPanel != null) {
				TileMap map = tileMapPanel.getTiledMap();
				if (map != null) {
					map.setClipBounds((int) (getWidth()), (int) (getHeight()));
				}
			}
		}

		public void componentShown(ComponentEvent arg0) {
			// TODO Auto-generated method stub

		}

	}
}
